/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.validator.engine;

import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import ch.trackedbean.common.*;
import ch.trackedbean.validator.*;
import ch.trackedbean.validator.annotations.*;

/**
 * Information about a depth property.<br>
 * Used to represent a {@link DeepValidation} property.
 * 
 * @author M. Hautle
 */
public class DepthValidator {
	/** The property name. */
	protected final String name;

	/** The message for an invalid property. */
	protected final IErrorDescription message;

	/** The method to read this property. */
	protected final Method read;

	/**
	 * Default constructor.
	 * 
	 * @param name The property name
	 * @param message The message for an invalid property
	 * @param read The getter method
	 */
	public DepthValidator(String name, String message, Method read) {
		this.name = name;
		this.message = new ErrorDescription(message);
		this.read = read;
	}

	/**
	 * Validates this property on the given bean.
	 * 
	 * @param bean The bean
	 * @param context The context
	 * @return True if this property contains errors
	 */
	public boolean validate(Object bean, IValidationContext context) {
		boolean err = false;
		final Object value = getValue(bean);
		// validate the content if it's non null and if it haven't been validated
		if (value == null || !context.stepIntoObject(name, value))
			return false;
		// step down the structure
		if (value instanceof Collection) {
			err |= validate((Collection) value, context);
		} else if (value instanceof Map) {
			err |= validate((Map) value, context);
		} else if (value.getClass().isArray()) {
			err |= validate((Object[]) value, context);
		} else {
			final IValidator val = ValidatorManager.getValidator(value.getClass());
			err |= val.validate(value, context);
		}
		context.stepOutOfObject(value, err ? message : null);
		return err;
	}

	/**
	 * Validates the content of an array.
	 * 
	 * @param array The array
	 * @param context The context
	 * @return True if the validated array contains an error
	 */
	protected boolean validate(Object[] array, IValidationContext context) {
		boolean err = false;
		for (int i = 0; i < array.length; i++) {
			if (array[i] == null || !context.stepIntoCollectionElement(i, array[i]))
				continue;
			final IValidator val = ValidatorManager.getValidator(array[i].getClass());
			err |= val.validate(array[i], context);
			context.stepOutOfCollectionElement();
		}
		return err;
	}

	/**
	 * Validates the content of an list.
	 * 
	 * @param list The list
	 * @param context The context
	 * @return True if the validated list contains an error
	 */
	protected boolean validate(Collection list, IValidationContext context) {
		boolean err = false;
		final Iterator it = list.iterator();
		for (int i = 0; it.hasNext(); i++) {
			final Object value = it.next();
			if (value == null || !context.stepIntoCollectionElement(i, value))
				continue;
			final IValidator val = ValidatorManager.getValidator(value.getClass());
			err |= val.validate(value, context);
			context.stepOutOfCollectionElement();
		}
		return err;
	}

	/**
	 * Validates the content of an map.
	 * 
	 * @param map The map
	 * @param context The context
	 * @return True if the validated map contains an error
	 */
	protected boolean validate(Map<?, ?> map, IValidationContext context) {
		boolean err = false;
		for (Entry e : map.entrySet()) {
			final Object value = e.getValue();
			if (value == null || !context.stepIntoMapElement(e.getKey(), value))
				continue;
			final IValidator val = ValidatorManager.getValidator(value.getClass());
			err |= val.validate(value, context);
			context.stepOutOfMapElement();
		}
		return err;
	}

	/**
	 * Returns the value of the property.
	 * 
	 * @param bean The bean on which to invoke the getter
	 * @return The value of the given property
	 * @throws TechnicalException If something went wrong
	 */
	protected Object getValue(Object bean) throws TechnicalException {
		try {
			return read.invoke(bean);
		} catch (Exception e) {
			throw new TechnicalException("Error while reading " + read.getName() + " from " + bean, e);
		}
	}
}